#include "StdAfx.hpp"
#include "Tokenizer2.hpp"
#include "CharReader2.hpp"

enum NUMBER_TYPE
{
	NUMBER_INTEGER_DEC,
	NUMBER_INTEGER_HEX,
	NUMBER_INTEGER_BIN,
	NUMBER_FLOAT,
	NUMBER_COUNT
};

const char * Tokenizer2::TOKEN_TYPE_NAMES[] = {
	"End of file",
	"Symbol",
	"Identifier",
	"Integer",
	"Float",
	"String",
	"Char",
};

Tokenizer2::Tokenizer2(CharReader2 &cr)
: m_cr(cr)
, m_Type(TOKEN_COUNT)
{
	Next();
}

void Tokenizer2::Next()
{
	SkipWhitespace();

	char ch;
	if (m_cr.ReadChar(ch))
	{
		if (ch == '+' || ch == '-' || ch == '*' || ch == '/'
			|| ch == '%' || ch == '(' || ch == ')' || ch == '~'
			|| ch == '&' || ch == '|' || ch == '^' || ch == '<'
			|| ch == '>' || ch == '!' || ch == '=' || ch == ','
			|| ch == '?' || ch == ':' || ch == ';' || ch == '.'
			|| ch == '{' || ch == '}' || ch == '[' || ch == ']')
		{
			m_Type = TOKEN_SYMBOL;
			m_Str.clear();
			m_Str += ch;

			switch (ch)
			{
			case '<':
				if (m_cr.PeekChar(ch) && (ch == '<' || ch == '='))
				{
					m_cr.SkipChar(); m_Str += ch;
					if (ch == '<' && m_cr.PeekChar(ch) && ch == '=') { m_cr.SkipChar(); m_Str += ch; }
				}
				break;
			case '>':
				if (m_cr.PeekChar(ch) && (ch == '>' || ch == '='))
				{
					m_cr.SkipChar(); m_Str += ch;
					if (ch == '>' && m_cr.PeekChar(ch) && ch == '=') { m_cr.SkipChar(); m_Str += ch; }
				}
				break;
			case '|':
				if (m_cr.PeekChar(ch) && ch == '|' || ch == '=') { m_cr.SkipChar(); m_Str += ch; }
				break;
			case '&':
				if (m_cr.PeekChar(ch) && ch == '&' || ch == '=') { m_cr.SkipChar(); m_Str += ch; }
				break;
			case '!':
				if (m_cr.PeekChar(ch) && ch == '=') { m_cr.SkipChar(); m_Str += ch; }
				break;
			case '+':
				if (m_cr.PeekChar(ch) && (ch == '=' || ch == '+')) { m_cr.SkipChar(); m_Str += ch; }
				break;
			case '-':
				if (m_cr.PeekChar(ch) && (ch == '=' || ch == '-')) { m_cr.SkipChar(); m_Str += ch; }
				break;
			// Chars that can have additional '=', like "+="
			case '=':
			case '*':
			case '/':
			case '%':
			case '^':
				if (m_cr.PeekChar(ch) && ch == '=') { m_cr.SkipChar(); m_Str += ch; }
				break;
			}
		}
		else if (ch >= 'a' && ch <= 'z' || ch >= 'A' && ch <= 'Z' || ch == '_')
		{
			m_Type = TOKEN_IDENTIFIER;
			m_Str.clear();
			m_Str += ch;
			while (m_cr.PeekChar(ch) && ( ch >= 'a' && ch <= 'z' || ch >= 'A' && ch <= 'Z' || ch >= '0' && ch <= '9' || ch == '_' ))
			{
				m_cr.SkipChar();
				m_Str += ch;
			}
		}
		else if (ch >= '0' && ch <= '9')
		{
			m_Type = TOKEN_INTEGER;
			m_Str.clear();
			m_Str += ch;
			NUMBER_TYPE numberType = NUMBER_INTEGER_DEC;
			if (ch == '0' && m_cr.PeekChar(ch))
			{
				if (ch == 'x' || ch == 'X')
				{
					m_Str += ch;
					m_cr.SkipChar();
					numberType = NUMBER_INTEGER_HEX;
				}
				else if (ch == 'b' || ch == 'B')
				{
					m_Str += ch;
					m_cr.SkipChar();
					numberType = NUMBER_INTEGER_BIN;
				}
			}

			switch (numberType)
			{
			case NUMBER_INTEGER_DEC:
				{
					/* 1234.56e-10
					00001112333
					*/
					int floatStage = 0;
					while (m_cr.PeekChar(ch))
					{
						if (ch >= '0' && ch <= '9')
						{
							m_Str += ch;
							m_cr.SkipChar();
							if (floatStage == 2)
								floatStage = 3;
						}
						else if (ch == '.' && floatStage == 0)
						{
							m_Str += ch;
							m_cr.SkipChar();
							floatStage = 1;
						}
						else if ((ch == 'e' || ch == 'E' || ch == 'd' || ch == 'D') && floatStage < 2)
						{
							m_Str += ch;
							m_cr.SkipChar();
							floatStage = 2;
						}
						else if (ch == '-' && floatStage == 2)
						{
							m_Str += ch;
							m_cr.SkipChar();
							floatStage = 3;
						}
						else if (ch >= 'a' && ch <= 'z' || ch >= 'A' && ch <= 'Z')
							throw Error("Tokenizer error in number");
						else
							break;
					}
					if (floatStage > 0)
					{
						numberType = NUMBER_FLOAT;
						m_Type = TOKEN_FLOAT;
					}
				}
				break;
			case NUMBER_INTEGER_HEX:
				while (m_cr.PeekChar(ch))
				{
					if (ch >= '0' && ch <= '9' || ch >= 'a' && ch <= 'f' || ch >= 'A' && ch <= 'F')
					{
						m_Str += ch;
						m_cr.SkipChar();
					}
					else if (ch >= 'g' && ch <= 'z' || ch >= 'G' && ch <= 'Z')
						throw Error("Tokenizer error in hexadecimal number");
					else
						break;
				}
				break;
			case NUMBER_INTEGER_BIN:
				while (m_cr.PeekChar(ch))
				{
					if (ch == '0' || ch == '1')
					{
						m_Str += ch;
						m_cr.SkipChar();
					}
					else if (ch >= 'a' && ch <= 'z' || ch >= 'A' && ch <= 'Z' || ch >= '2' && ch <= '9')
						throw Error("Tokenizer error in binary number");
					else
						break;
				}
				break;
			default:
				assert(0);
			}
		}
		else if (ch == '\'')
		{
			m_Type = TOKEN_CHAR;
			m_Str.clear();
			m_Str += ParseStringChar();
			ch = m_cr.MustReadChar();
			if (ch != '\'')
				throw Error("Expected (\')");
		}
		else if (ch == '"')
		{
			m_Type = TOKEN_STRING;
			m_Str.clear();
			while ((ch = m_cr.MustPeekChar()) != '"')
				m_Str += ParseStringChar();
			m_cr.SkipChar();
		}
		else
			throw Error("Tokenizer error");
	}
	else
	{
		m_Type = TOKEN_EOF;
		m_Str.clear();
	}
}

void Tokenizer2::AssertToken(TOKEN_TYPE type)
{
	if (!QueryToken(type))
		throw Error("Expected " + string(TOKEN_TYPE_NAMES[type]));
}

void Tokenizer2::AssertToken(TOKEN_TYPE type1, TOKEN_TYPE type2)
{
	if (!QueryToken(type1, type2))
		throw Error("Expected " + string(TOKEN_TYPE_NAMES[type1]) + " or " + string(TOKEN_TYPE_NAMES[type2]));
}

void Tokenizer2::AssertSymbol(char ch)
{
	if (!QuerySymbol(ch))
		throw Error("Expected symbol (" + CharToStrR(ch) + ")");
}

void Tokenizer2::AssertIdentifier(const string &s)
{
	if (!QueryIdentifier(s))
		throw Error("Expected identifier " + s);
}

void Tokenizer2::CreateError()
{
	CreateError("Syntax error");
}

void Tokenizer2::CreateError(const string &msg)
{
	throw Error(msg);
}



void Tokenizer2::SkipWhitespace()
{
	char ch;
	while (m_cr.PeekChar(ch))
	{
		if (CharIsWhitespace(ch))
			m_cr.SkipChar();
		else if (ch == '/')
		{
			Iterator it = GetIterator();
			m_cr.SkipChar();
			if (m_cr.PeekChar(ch))
			{
				// Single line comment: // ...
				if (ch == '/')
				{
					while (m_cr.ReadChar(ch) && ch != '\n') { }
				}
				// Multiline comment /* ... */
				else if (ch == '*')
				{
					for (;;)
					{
						if (!m_cr.ReadChar(ch))
							CreateError("Unexpected end of file inside comment");
						if (ch == '*' && m_cr.PeekChar(ch) && ch == '/')
						{
							m_cr.SkipChar();
							break;
						}
					}
				}
				else
				{
					SetIterator(it);
					break;
				}
			}
			else
			{
				SetIterator(it);
				break;
			}
		}
		else
			break;
	}
}

char Tokenizer2::ParseStringChar()
{
	char ch = m_cr.MustReadChar();
	if (ch == '\\')
	{
		ch = m_cr.MustReadChar();
		switch (ch)
		{
		case 'n':  return '\n';
		case 't':  return '\t';
		case 'v':  return '\v';
		case 'b':  return '\b';
		case 'r':  return '\r';
		case 'f':  return '\f';
		case 'a':  return '\a';
		case '\\': return '\\';
		case '?':  return '\?';
		case '\'': return '\'';
		case '"':  return '\"';
		case '0':  return '\0';
		case 'x':
			{
				string num;
				ch = m_cr.MustPeekChar();
				while (ch >= '0' && ch <= '9' || ch >= 'a' && ch <= 'z' || ch >= 'A' && ch <= 'Z')
				{
					m_cr.SkipChar();
					num += ch;
					ch = m_cr.MustPeekChar();
				}
				if (StrToUint((BYTE*)&ch, num, 16) != 0)
					throw Error("Invalid hexadecimal character code");
				return ch;
			}
		case '1': case '2': case '3':
		case '4': case '5': case '6':
		case '7': case '8': case '9':
			{
				string num;
				num += ch;
				ch = m_cr.MustPeekChar();
				while (ch >= '0' && ch <= '9')
				{
					m_cr.SkipChar();
					num += ch;
					ch = m_cr.MustPeekChar();
				}
				if (StrToUint((BYTE*)&ch, num, 10) != 0)
					throw Error("Invalid decimal character code");
				return ch;
			}
		default:
			throw Error("Invalid escape sequence");
			return 0;
		}
	}
	else
		return ch;
}
